﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace TPDF
{
    /// <summary>
    /// 按照步骤 1a 或 1b 操作，然后执行步骤 2 以在 XAML 文件中使用此自定义控件。
    ///
    /// 步骤 1a) 在当前项目中存在的 XAML 文件中使用该自定义控件。
    /// 将此 XmlNamespace 特性添加到要使用该特性的标记文件的根
    /// 元素中:
    ///
    ///     xmlns:MyNamespace="clr-namespace:TPDF"
    ///
    ///
    /// 步骤 1b) 在其他项目中存在的 XAML 文件中使用该自定义控件。
    /// 将此 XmlNamespace 特性添加到要使用该特性的标记文件的根
    /// 元素中:
    ///
    ///     xmlns:MyNamespace="clr-namespace:TPDF;assembly=TPDF"
    ///
    /// 您还需要添加一个从 XAML 文件所在的项目到此项目的项目引用，
    /// 并重新生成以避免编译错误:
    ///
    ///     在解决方案资源管理器中右击目标项目，然后依次单击
    ///     “添加引用”->“项目”->[浏览查找并选择此项目]
    ///
    ///
    /// 步骤 2)
    /// 继续操作并在 XAML 文件中使用控件。
    ///
    ///     <MyNamespace:TPdfViewer/>
    ///
    /// </summary>
    public class TPdfViewer : Control, IScrollInfo
    {



        Size workAreaSize = new Size(0, 0);
        Size viewportSize = new Size(0, 0);

        #region Private fields

        /// <summary>
        /// List of rendered pages. Used to examine click, touch, ...
        /// </summary>

        /// <summary>
        /// Permission for horizontal scroll.
        /// </summary>
        private bool _canHorizontallyScroll;

        /// <summary>
        /// Permission for vertical scroll.
        /// </summary>
        private bool _canVerticallyScroll;

        /// <summary>
        /// Offset of viewport in x axis.
        /// </summary>
        private double _horizontalOffset;

        /// <summary>
        /// Offset of viewport in y axis.
        /// </summary>
        private double _verticalOffset;

        /// <summary>
        /// The area where all pages will fit.
        /// </summary>
        private Size _workArea = new Size(0, 0);

        /// <summary>
        /// The area visible to user.
        /// </summary>
        private Size _viewport = new Size(0, 0);

        /// <summary>
        /// Zoom at manipulation start.
        /// </summary>
        private double _startManipulationZoom;

        /// <summary>
        /// Horizontal offset at manipulation start.
        /// </summary>
        private double _startManipulationHorizontalOffset;

        /// <summary>
        /// Vertical offset at manipulation start.
        /// </summary>
        private double _startManipulationVerticalOffset;

        /// <summary>
        /// <c>true</c> if touch manipulation for zoom is active.
        /// </summary>
        private bool _zoomManipulationActive = false;

        private double _oldZoomFactor = -1d;

        /// <summary>
        /// Variable used to support scroll through mouse and stylus dragging.
        /// </summary>
        private Point _startDragPoint = new Point(-1, -1);

        /// <summary>
        /// Variable used to support scroll through mouse and stylus dragging.
        /// </summary>
        private double _startHorizontalOffset;

        /// <summary>
        /// Variable used to support scroll through mouse and stylus dragging.
        /// </summary>
        private double _startVerticalOffset;

        #endregion Private fields
        public List<TPage> Pages
        {
            get { return (List<TPage>)GetValue(PagesProperty); }
            set { SetValue(PagesProperty, value); }
        }

        public bool CanHorizontallyScroll
        {
            get => _canHorizontallyScroll;
            set
            {
                if (_canHorizontallyScroll != value)
                {
                    _canHorizontallyScroll = value;
                    InvalidateVisual();
                    ScrollOwner?.InvalidateScrollInfo();
                }
            }
        }

        /// <summary>
        /// Gets or sets a value that indicates whether scrolling on the vertical axis is possible.
        /// </summary>
        /// <value><c>true</c> if scrolling is possible; otherwise, <c>false</c>.
        /// This property has no default value.</value>
        public bool CanVerticallyScroll
        {
            get => _canVerticallyScroll;
            set
            {
                if (_canVerticallyScroll != value)
                {
                    _canVerticallyScroll = value;
                    InvalidateVisual();
                    ScrollOwner?.InvalidateScrollInfo();
                }
            }
        }

        public double ExtentWidth => workAreaSize.Width;

        public double ExtentHeight => workAreaSize.Height;

        public double ViewportWidth => viewportSize.Width;

        public double ViewportHeight => viewportSize.Height;
        public double HorizontalOffset
        {
            get => _horizontalOffset;
            private set
            {
                value = Math.Max(0, Math.Min(value, ExtentWidth - ViewportWidth));
                if (_horizontalOffset != value)
                {
                    _horizontalOffset = value;
                    InvalidateVisual();
                    ScrollOwner?.InvalidateScrollInfo();
                }
            }
        }
        public double VerticalOffset
        {
            get => _verticalOffset;
            private set
            {
                value = Math.Max(0, Math.Min(value, ExtentHeight - ViewportHeight));
                if (_verticalOffset != value)
                {
                    _verticalOffset = value;
                    InvalidateVisual();
                    ScrollOwner?.InvalidateScrollInfo();
                }
            }
        }

        public ScrollViewer ScrollOwner { get; set; }

        // Using a DependencyProperty as the backing store for Pages.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty PagesProperty =
            DependencyProperty.Register("Pages", typeof(List<TPage>), typeof(TPdfViewer), new PropertyMetadata(new List<TPage>(), new PropertyChangedCallback(PagesPropertyChanged)));

        private static void PagesPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {

            TPdfViewer pdfViewer = d as TPdfViewer;
            if (pdfViewer != null)
            {
                var pages = e.NewValue as List<TPage>;
                var x = pages.Max(p => p.Width);
                var y = pages.Sum(p => p.Height) + 3 * pages.Count;
                var tSize = new Size(x, y);
                if (pdfViewer.workAreaSize != tSize)
                    pdfViewer.workAreaSize = tSize;
                pdfViewer.Update();
                pdfViewer.LineDown();
            }
        }




        public void Update()
        {
            Application.Current.Dispatcher.Invoke(() => InvalidateVisual());
        }
        static TPdfViewer()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(TPdfViewer), new FrameworkPropertyMetadata(typeof(TPdfViewer)));
        }
        protected override void OnRender(DrawingContext drawingContext)
        {
            if (Pages != null)
                RenderPage(drawingContext);
            else
                base.OnRender(drawingContext);
        }
        protected override Size ArrangeOverride(Size arrangeBounds)
        {
            Size size = base.ArrangeOverride(DesiredSize);
            if (Pages.Count > 0)
            {
                var x = Pages.Max(p => p.Width);
                var y = Pages.Sum(p => p.Height) + 3 * Pages.Count;
                var tSize = new Size(x, y);

                UpdateWorkAreaSize(tSize);
            }
            UpdateViewportSize(arrangeBounds);
            return size;

        }
        protected override Size MeasureOverride(Size constraint)
        {
            var newSize = new Size(
             double.IsInfinity(constraint.Width) ? 0d : constraint.Width,
             double.IsInfinity(constraint.Height) ? 0d : constraint.Height);
            UpdateViewportSize(newSize);
            return newSize;
        }
        private void UpdateWorkAreaSize(Size newSize)
        {
            if (workAreaSize == newSize)
            {
                return;
            }

            workAreaSize = newSize;
            ScrollOwner?.InvalidateScrollInfo();
        }
        private void UpdateViewportSize(Size newSize)
        {
            if (viewportSize == newSize)
            {
                return;
            }

            viewportSize = newSize;
            ScrollOwner?.InvalidateScrollInfo();
        }
        WriteableBitmap bitmap;
        private void RenderPage(DrawingContext drawingContext)
        {
            drawingContext.DrawRectangle(Background, null, new Rect(0, 0, ViewportWidth, ViewportHeight));
            if (Pages == null) return;
            var page = Pages.FirstOrDefault();
            if (page == null) return;
            bitmap = new WriteableBitmap((int)page.Width, (int)page.Height, 72, 72, PixelFormats.Bgra32, null);
            page.Render(bitmap);
            drawingContext.DrawImage(bitmap, new Rect() { Height = page.Height, Width = page.Width, X = 0, Y = 0 });
        }
        public void RenderPage1(DrawingContext drawingContext)
        {
            for (int i = 0; i < Pages.Count; i++)
            {
                WriteableBitmap bitmap = new WriteableBitmap((int)Pages[i].Width, (int)Pages[i].Height, 72, 72, PixelFormats.Bgra32, null);
                Pages[i].Render(bitmap);
                drawingContext.DrawImage(bitmap, new Rect() { Height = Pages[i].Height, Width = Pages[i].Width, X = 0, Y = 0 });
            }
        }

        public void LineDown()
        {
            VerticalOffset += ViewportHeight / 10;
        }

        /// <summary>
        /// Scrolls left within content by one logical unit.
        /// </summary>
        public void LineLeft()
        {
            HorizontalOffset -= ViewportWidth / 10;
        }

        /// <summary>
        /// Scrolls right within content by one logical unit.
        /// </summary>
        public void LineRight()
        {
            HorizontalOffset += ViewportWidth / 10;
        }

        /// <summary>
        /// Shift the content offset one line up.
        /// </summary>
        public void LineUp()
        {
            VerticalOffset -= ViewportHeight / 10;
        }

        /// <summary>
        /// Scrolls down within content after a user clicks the wheel button on a mouse.
        /// </summary>
        /// <remarks>Don't handle mouse wheel input from the ScrollViewer, the mouse wheel is used for zooming in and out, not for manipulating the scrollbars.</remarks>
        public void MouseWheelDown()
        {
            if ((Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control)
            {
                // Zoom
            }
            else if ((Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift)
            {
                // Move right
                LineRight();
            }
            else
            {
                // Move down
                LineDown();
            }
        }

        /// <summary>
        /// Scrolls left within content after a user clicks the wheel button on a mouse.
        /// </summary>
        /// <remarks>Don't handle mouse wheel input from the ScrollViewer, the mouse wheel is used for zooming in and out, not for manipulating the scrollbars.</remarks>
        public void MouseWheelLeft()
        {
            if ((Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control)
            {
                // Zoom
            }
            else
            {
                // Move up
                LineLeft();
            }
        }

        /// <summary>
        /// Scrolls right within content after a user clicks the wheel button on a mouse.
        /// </summary>
        /// <remarks>Don't handle mouse wheel input from the ScrollViewer, the mouse wheel is used for zooming in and out, not for manipulating the scrollbars.</remarks>
        public void MouseWheelRight()
        {
            if ((Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control)
            {
                // Zoom
            }
            else
            {
                // Move
                LineRight();
            }
        }

        /// <summary>
        /// Scrolls up within content after a user clicks the wheel button on a mouse.
        /// </summary>
        /// <remarks>Don't handle mouse wheel input from the ScrollViewer, the mouse wheel is used for zooming in and out, not for manipulating the scrollbars.</remarks>
        public void MouseWheelUp()
        {
            if ((Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control)
            {
                // Zoom
            }
            else if ((Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift)
            {
                // Move right
                LineLeft();
            }
            else
            {
                // Move down
                LineUp();
            }
        }

        /// <summary>
        /// Scrolls down within content by one page.
        /// </summary>
        public void PageDown()
        {
            VerticalOffset += ViewportHeight;
        }

        /// <summary>
        /// Scrolls left within content by one page.
        /// </summary>
        public void PageLeft()
        {
            HorizontalOffset -= ViewportWidth;
        }

        /// <summary>
        /// Scrolls up within content by one page.
        /// </summary>
        public void PageRight()
        {
            HorizontalOffset += ViewportWidth;
        }

        /// <summary>
        /// Scrolls up within content by one page.
        /// </summary>
        public void PageUp()
        {
            VerticalOffset -= ViewportHeight;
            //Console.WriteLine(VerticalOffset);
        }

        /// <summary>
        /// Forces content to scroll until the coordinate space of a <see cref="Visual"/> object is visible.
        /// </summary>
        /// <param name="visual">A <see cref="Visual"/> that becomes visible.</param>
        /// <param name="rectangle">A bounding rectangle that identifies the coordinate space to make visible.</param>
        /// <returns>A <see cref="Rect"/> that is visible.</returns>
        public Rect MakeVisible(Visual visual, Rect rectangle)
        {
            // ToDo: Not implemented
            return rectangle;
        }

        /// <summary>
        /// Sets the amount of horizontal offset.
        /// </summary>
        /// <param name="offset">The degree to which content is horizontally offset from the containing viewport.</param>
        public void SetHorizontalOffset(double offset)
        {
            HorizontalOffset = offset;
        }

        /// <summary>
        /// Sets the amount of vertical offset.
        /// </summary>
        /// <param name="offset">The degree to which content is vertically offset from the containing viewport.</param>
        public void SetVerticalOffset(double offset)
        {
            VerticalOffset = offset;
        }



        protected override void OnManipulationDelta(ManipulationDeltaEventArgs e)
        {
            if (e != null && e.CumulativeManipulation != null)
            {
                if ((e.Manipulators.Count() == 1 || e.IsInertial) && !_zoomManipulationActive)
                {
                    HorizontalOffset = _startManipulationHorizontalOffset - e.CumulativeManipulation.Translation.X;
                    VerticalOffset = _startManipulationVerticalOffset - e.CumulativeManipulation.Translation.Y;
                }
                else if (e.Manipulators.Count() == 2)
                {
                    _zoomManipulationActive = true;
                    var factor = (e.CumulativeManipulation.Scale.X + e.CumulativeManipulation.Scale.Y) / 2;
                    var newZoom = _startManipulationZoom * factor;
                    //PDFZoomComponent.CurrentZoomFactor = newZoom;
                }
            }

            base.OnManipulationDelta(e);
        }
        protected override void OnMouseMove(MouseEventArgs e)
        {
            base.OnMouseMove(e);

            if (e != null)
            {
                if (e.LeftButton == MouseButtonState.Pressed)
                {
                    if (_startDragPoint.X != -1 && _startDragPoint.Y != -1)
                    {
                        var newDragPoint = e.GetPosition(this);
                        HorizontalOffset = _startHorizontalOffset + _startDragPoint.X - newDragPoint.X;
                        VerticalOffset = _startVerticalOffset + _startDragPoint.Y - newDragPoint.Y;
                    }
                }
                else
                {
                    var point = e.GetPosition(this);
                    //var link = GetLinkOnPosition(point);
                    //if (link != null)
                    //{
                    //    Cursor = Cursors.Hand;
                    //    return;
                    //}
                }
            }

            Cursor = Cursors.Arrow;
        }
    }
}
